/* Copyright 2024 Coremail
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import dataPreferences from '@ohos.data.preferences';
import { BaseConstants } from '../constants/BaseConstants';
import type { BusinessError } from '@ohos.base';
import type common from '@ohos.app.ability.common';
import cryptoFramework from '@ohos.security.cryptoFramework';
import { base64Decode, base64Encode, decodeCharset, encodeUtf8 } from '@coremail/mail_base/src/main/ets/utils/encodings';
import { getLogger } from '@coremail/mail_base';

const logger = getLogger('account')

let preference: dataPreferences.Preferences;

// 加密 s => utf-8 => encrypt => base64 => result
// 解密 s => base64 => decrypt => utf-8 => result
// 加密后输出base64的字符串
export async function encrypt(content: string, isEncrypt = true): Promise<string> {
  const b = encodeUtf8(content);
  const b2 = await doEncrypt(b, true);
  const s = base64Encode(b2);
  return s;
}

// 输入是base64的字符串
export async function decrypt(content: string, isEncrypt = true): Promise<string> {
  const b = base64Decode(content);
  const b2 = await doEncrypt(b, false);
  const s = decodeCharset(b2, 'utf-8');
  return s;
}

async function doEncrypt(content: Uint8Array, isEncrypt = true): Promise<Uint8Array> {
  // content
  logger.info("encrypt", isEncrypt);
  try {
    const cipherAlgName = '3DES192';
    const blob = {
      data: encodeUtf8("123456789012345@coremail")
    }

    const symKeyGenerator = cryptoFramework.createSymKeyGenerator(cipherAlgName);
    const key = await symKeyGenerator.convertKey(blob)

    const cipher = cryptoFramework.createCipher(cipherAlgName);
    logger.info(`cipher algName: ${cipher.algName}`);
    await cipher.init(
      isEncrypt ?
        cryptoFramework.CryptoMode.ENCRYPT_MODE :
        cryptoFramework.CryptoMode.DECRYPT_MODE,
      key,
      null);
    // await cipher.update({data: encodeUtf8(content)});
    const res = await cipher.doFinal({ data: content })
    return res.data;
  } catch (error) {
    // logger.error("createCipher failed" + error.code + ", message =" + error.message);
    logger.error("createCipher failed", error);
    return content;
  }
}

export class AccountPreference {
  /**
   * init Preferences
   * @returns
   */
  private async getPreferencesFromStorage(context: common.Context): Promise<void> {
    try {
      preference = await dataPreferences.getPreferences(context, BaseConstants.ACCOUNT_PREFERENCES_NAME);
    } catch (err) {
      logger.error("get Preference ERROR " + err)
    }
  }

  /**
   *
   * @param key data key
   * @param value data value
   * @returns save result
   */
  async saveData(context: common.Context, key: string, value: string): Promise<void> {
    if (!preference) {
      await this.getPreferencesFromStorage(context);
    }
    try {
      let mKey = await encrypt(key)
      let mContent = await encrypt(value)
      preference.put(mKey, mContent).then(async () => {
        await preference.flush();
        logger.info("Preference put key is succeeded " + mKey + " value ： " + mContent);
      }).catch((err: BusinessError) => {
        logger.error("Preference save value reject. code =" + err.code + ", message =" + err.message);
      })
    } catch (err) {
      let code = (err as BusinessError).code;
      let message = (err as BusinessError).message;
      logger.error("Preference save value throw err code =" + code + ", message =" + message);
    }
  }

  async getValue(context: common.Context, key: string): Promise<string> {
    if (!preference) {
      await this.getPreferencesFromStorage(context);
    }
    try {
      let mKey = await encrypt(key)
      return preference.get(mKey, "")
        .then((value) => {
          return value as string
        }).then((content) => {
          if (content.length > 0) {
            return decrypt(content)
          }
        })
    } catch (err) {
      let code = (err as BusinessError).code;
      let message = (err as BusinessError).message;
      logger.error("Preference get value throw err code =" + code + ", message =" + message);
    }
  }

  /**
   *
   * @param context to get preference
   * @param key data key
   * @returns key exist in preference or not
   */
  async checkKey(context: common.Context, key: string): Promise<boolean> {
    if (!preference) {
      await this.getPreferencesFromStorage(context);
    }
    try {
      let mKey = await encrypt(key)
      return preference.has(mKey)
    } catch (err) {
      let code = (err as BusinessError).code;
      let message = (err as BusinessError).message;
      logger.error("Preference check key throw code =" + code + ", message =" + message);
    }
  }

  async getAll(context: common.Context): Promise<string[]> {
    if (!preference) {
      await this.getPreferencesFromStorage(context);
    }
    try {
      let value = preference.getAllSync();
      let allKeys: string[] = getObjKeys(value);
      if (allKeys.length > 0) {
        let array: string[] = []
        let mSyncItem = await encrypt(BaseConstants.KEY_SYNC)
        await Promise.all(allKeys.map(async (item: string) => {
          if (item != mSyncItem) {
            const element: string = await decrypt(value[item] as string)
            array.push(element)
          }
        }))
        return array
      }
    } catch (err) {
      let code = (err as BusinessError).code;
      let message = (err as BusinessError).message;
      logger.error("Failed to get all key-values. code =" + code + ", message =" + message);
    }
  }


  async deleteData(context: common.Context, key: string): Promise<void> {
    if (!preference) {
      await this.getPreferencesFromStorage(context);
    }
    try {
      let mKey = await encrypt(key)
      preference.delete(mKey).then(async () => {
        await preference.flush();
      }).catch((err: BusinessError) => {
        logger.error("Preference Failed to delete key. code =" + err.code + ", message =" + err.message);
      })
    } catch (err) {
      let code = (err as BusinessError).code;
      let message = (err as BusinessError).message;
      logger.error("Preference Failed to delete throw code =" + code + ", message =" + message);
    }
  }

  async deletePreferences(context: common.Context): Promise<void> {
    if (!preference) {
      await this.getPreferencesFromStorage(context);
    }
    try {
      await dataPreferences.deletePreferences(context, BaseConstants.ACCOUNT_PREFERENCES_NAME);
      preference = undefined
    } catch (err) {
      let code = (err as BusinessError).code;
      let message = (err as BusinessError).message;
      logger.error("Preference Failed to deletePreferences.throw code =" + code + ", message =" + message);
    }
  }
}

function getObjKeys(obj: Object): string[] {
  let keys = Object.keys(obj);
  return keys;
}

